/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package WASACommunicator;

import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
import jWASA.Sample;
import jWASA.Sensor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TooManyListenersException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author gianmariospacagna
 */
public class WASACommunicator implements SerialPortEventListener {
    //config internal parameters

    static Enumeration portList;
    static CommPortIdentifier portId;
    static InputStream inputStream;
    static SerialPort serialPort;
    static String myStr;
    static OutputStream outputStream;
    boolean portFound;
    String defaultPort;
    boolean isenabled;
    byte[] readBuffer;
    byte[] messageBuffer;
    byte[] okMessage;
    byte[] errorMessage;
    int numBytes;
    short messagePtrIn;
    short messagePtrOut;
    short tempPtr;
    short sx;
    int x;
    private final boolean responseFlag;
    //customized parameters
    /*Sensor sensorEKG;
    Sensor sensorTemp;
    Sensor sensorLight;*/
    HashMap<Sensor, ArrayList<Sample>> samples;
    Set<Sensor> activeSensors;
    int rate;
    int totDim;
    StreamState prevState;

    /**
     * DefaultPort is the path of your local USB port
     * @param defaultPort 
     */
    public WASACommunicator(String defaultPort) throws TooManyListenersException {
        this.defaultPort = defaultPort;

        this.isenabled = true;
        this.readBuffer = new byte[256];
        this.messageBuffer = new byte[2048];
        this.okMessage = new byte[6];
        this.errorMessage = new byte[9];
        this.numBytes = 0;
        this.messagePtrIn = 0;
        this.messagePtrOut = 0;
        this.tempPtr = 0;
        this.sx = 0;
        this.x = 0;
        this.responseFlag = false;

        configurePort();
        initializeTemplate();
        /*
        sensorEKG = new SensorEKG("EKG", "Electrocardiogram Sensor", 2, 1);
        sensorTemp = new Sensor("TEMP", "Temperature Sensor", 4, 1);
        sensorLight = new Sensor("LIGHT", "Light Sensor", 3, 1);
         */

        rate = 0;
        totDim = 0;

        samples = new HashMap<Sensor, ArrayList<Sample>>();
    }

    /**
     * Configure the port of WASA USB communication
     */
    private void configurePort() throws TooManyListenersException {
        portList = CommPortIdentifier.getPortIdentifiers();

        while (portList.hasMoreElements()) {
            portId = (CommPortIdentifier) portList.nextElement();

            if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {

                if (portId.getName().equals(defaultPort)) {
                    System.out.println("Found port " + defaultPort);
                    portFound = true;
                    break;
                }
            }
        }


        if (portFound == false) {  // if we can't find requested COM port
            System.out.println("port " + defaultPort + " not found.");
            System.exit(-1);
        }

        try {
            serialPort = (SerialPort) portId.open("MyATCmdApp", 2000);

        } catch (PortInUseException e) {
        }


        try {
            serialPort.enableReceiveTimeout(100);
        } catch (UnsupportedCommOperationException e) {
        }



        isenabled = serialPort.isReceiveTimeoutEnabled();

        if (isenabled == true) {

            System.out.println("Yes, we support timeout enabling!");
        } else {
            System.out.println("No, we don't support timeouts!");
        }


        try {
            inputStream = serialPort.getInputStream();
        } catch (IOException e) {
        }

        try {
            outputStream = serialPort.getOutputStream();
        } catch (IOException e) {
        }


        try {
            serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8,
                    SerialPort.STOPBITS_1,
                    SerialPort.PARITY_NONE);
        } catch (UnsupportedCommOperationException e) {
        }



        try {
            serialPort.notifyOnOutputEmpty(true);
        } catch (Exception e) {
            System.out.println("Error setting event notification");
            System.out.println(e.toString());
            System.exit(-1);
        }

        serialPort.addEventListener(this);

        serialPort.notifyOnDataAvailable(true);

    }

    /**
     * Initialize the OK and Error Messages
     */
    private void initializeTemplate() {


        okMessage[0] = '\n';
        okMessage[1] = '\r';
        okMessage[2] = 'K';
        okMessage[3] = 'O';
        okMessage[4] = '\n';
        okMessage[5] = '\r';

        errorMessage[0] = '\n';
        errorMessage[1] = '\r';
        errorMessage[2] = 'R';
        errorMessage[3] = 'O';
        errorMessage[4] = 'R';
        errorMessage[5] = 'R';
        errorMessage[6] = 'E';
        errorMessage[7] = '\n';
        errorMessage[8] = '\r';
    }

    private void sendATCommand(String command) {
        try {
            outputStream.write(command.getBytes());
        } catch (IOException e) {
        }
    }

// Now, read the reply from the board.
    /**
     * Get the samples measured
     * @param s the sensor we want to take measurements
     * @return the samples measured since the last reading
     */
    public ArrayList<Sample> getSamples(Sensor s) {
        return samples.get(s);
    }

    public void activateStreamData(Set<Sensor> sensors, int rate) {
        samples.clear();

        int mask = generateMask(sensors);

        try {
            String str = "AT+OSA=" + mask + "\r\n";
            System.out.println("AT Command sent: " + str);
            outputStream.write(str.getBytes());
            str = "AT+OSR=" + rate + "\r\n";
            System.out.println("AT Command sent: " + str);
            outputStream.write(str.getBytes());
            activeSensors = sensors;
            this.rate = rate;
            this.totDim = totalDimension(sensors);
            for (Sensor s : sensors) {
                samples.put(s, new ArrayList<Sample>());
            }
            System.out.println("Total Dimension is: " + totDim);
        } catch (IOException e) {
        }

        // Now, read the reply from the board.

        try {
            serialPort.addEventListener(this);
        } catch (TooManyListenersException e) {
        }

        serialPort.notifyOnDataAvailable(true);
    }

    public void stopMeasurements() {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {

            case SerialPortEvent.BI:

            case SerialPortEvent.OE:

            case SerialPortEvent.FE:

            case SerialPortEvent.PE:

            case SerialPortEvent.CD:

            case SerialPortEvent.CTS:

            case SerialPortEvent.DSR:

            case SerialPortEvent.RI:

            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                break;

            case SerialPortEvent.DATA_AVAILABLE:
                long ts = System.nanoTime();

                BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
                String line = null;


                try {
                    while ((line = in.readLine()) != null) {
                        parseLine(line);
                    }
                } catch (IOException ex) {
                    //Logger.getLogger(WASACommunicator.class.getName()).log(Level.SEVERE, null, ex);
                }






                /*
                byte[] readBuffer;
                
                try {
                String rb = "";
                while (inputStream.available() > 0) {
                readBuffer = new byte[256];
                inputStream.read(readBuffer);
                String newData = new String(readBuffer);
                //System.out.println("NewData: "+newData);
                rb+=newData;
                
                }
                
                prevState = new StreamState(totDim, rate);
                parseData(rb, ts);
                System.out.println("###### END DATA AVILABLE EVENT #######");
                try {
                Thread.sleep(1000);
                } catch (InterruptedException ex) {
                Logger.getLogger(WASACommunicator.class.getName()).log(Level.SEVERE, null, ex);
                }
                } catch (IOException ex) {
                Logger.getLogger(WASACommunicator.class.getName()).log(Level.SEVERE, null, ex);
                }
                
                 */





                break;
        }
    }

    private int generateMask(Set<Sensor> sensors) {
        int mask = 0;
        for (Sensor s : sensors) {
            mask += Math.pow(2, s.getWasaId());
        }
        return mask;
    }

    private void addSamples(Set<Sensor> activeSensors, long[] values) {
        int k = 0;
        for (Sensor s : activeSensors) {
            int dim = s.getDim();
            long[] v = new long[dim];
            for (int i = 0; i < dim; i++) {
                v[i] = values[k++];
            }
            Sample sample = new Sample(System.currentTimeMillis(), v, s);
            System.out.println("WASAComm-> New Sample: "+sample.toString());
            samples.get(s).add(sample);
        }
    }

    private void parseData(String rb, long ts) throws IOException {
        System.out.println("Current ts: " + ts);
        System.out.println("Parse this read buffer:");
        System.out.println(rb);

        StringTokenizer st;
        //System.out.println("Rb: " + rb);

        String remainingRb = null;
        //System.out.println("TmpRb: " + tmpRb);
        //tmpRb += rb;
        //System.out.println("TmpRb+rb: " + tmpRb);
        String currentRow = new String(rb);


        if (rb.contains(":")) {
            int pos = rb.indexOf(":");
            remainingRb = rb.substring(pos + 1);
            currentRow = new String(rb.substring(0, pos));
        }
        int i = 0;
        System.out.println("Current Data Row: " + currentRow);
        st = new StringTokenizer(currentRow, ",");

        String val;
        long[] values = new long[totDim];
        while (st.hasMoreTokens()) {
            i++;
            val = st.nextToken();
            System.out.println("Token found: " + val);

            long parsedVal = Long.parseLong(val);
            values[i] = parsedVal;
            System.out.println("Val added: " + parsedVal);

        }
        //check if this is the last sub string
        if (remainingRb == null) {
            if (i == totDim) {
                addSamples(activeSensors, values);
                System.out.println("Last State complete");
            } else {
                prevState.index = i;
                prevState.prevVal = values;
                System.out.println("Last State uncomplete");
                System.out.println("Index: " + prevState.index + "\tprevVal: " + prevState.prevVal);
            }
        } else {
            parseData(remainingRb, ts + (long) (1 / rate));
        }
    }

    private int totalDimension(Set<Sensor> sensors) {
        int n = 0;

        for (Sensor s : sensors) {
            n += s.getDim();
        }

        return n;
    }

    private void parseLine(String line) {


        //System.out.println("Parse this line: " + line);
        try {
            if (line.startsWith("DATA ANL")) {
                String subLine = line.substring(10);
                subLine = trimSpace(subLine);
                StringTokenizer st = new StringTokenizer(subLine);
                //System.out.println("subLine is: \"" + subLine + "\"");

                int i = 0;
                String val;
                long[] values = new long[totDim];
                while (i < totDim) {

                    val = st.nextToken(",");
                    //System.out.println("Token " + i + " found: " + val);
                    int parsedVal = Integer.parseInt(val);

                    values[i] = parsedVal;
                    //System.out.println("Val added: " + parsedVal);



                    i++;
                }
                if (i == totDim) {
                    addSamples(activeSensors, values);
                    //System.out.println("All values filled: " + values);
                }
            }
        } catch (Exception e) {
            //e.printStackTrace();
        }
    }

    public String trimSpace(String s) {
        String[] choppedUpString = s.trim().split(" ");
        String trimmedString = "";
        for (int i = 0; i < choppedUpString.length; i++) {
            trimmedString = trimmedString + choppedUpString[i];
        }
        return trimmedString;
    }

    private class StreamState {

        String prevRb;
        long[] prevVal;
        int index;
        boolean complete;
        int rate;

        public StreamState(int n, int rate) {
            this.complete = false;
            this.prevRb = "";
            this.index = 0;
            this.prevVal = new long[n];
            this.rate = rate;
        }

        private void incrementIndex() {
            if (index < prevVal.length) {
                index++;
            } else {
                complete = true;
            }
        }

        private void addValue(Long value) {
            if (!complete) {
                prevVal[index] = value;
                incrementIndex();
            }
        }

        private boolean isComplete() {
            return complete;
        }

        private boolean isFloating() {
            return (index != 0);
        }

        private long[] getValues() {
            resetState();
            return prevVal;
        }

        private void resetState() {
            index = 0;
            complete = false;
        }

        private boolean isEmpty() {
            return (index == 0);
        }
    }
}
